
package async

import (
	"syscall"
	"syscall/fibril"
	"unsafe"
	"runtime"
)

// FIXME: Make sure all Go memory passed to C code is referenced somewhere.

type Call struct {
	id syscall.IpcCallidT
	data callData
}

func (c *Call) Arg(n int) syscall.Arg {
	return c.data.args[n]
}

func (c *Call) Method() syscall.Arg {
	return c.data.args[0]
}

func (c *Call) TaskID() syscall.TaskID {
	return c.data.caller
}

type exchangePtr unsafe.Pointer

type sessionPtr unsafe.Pointer

type Exchange struct {
	ptr exchangePtr
}

func createExchange(exchptr exchangePtr) *Exchange {
	if exchptr == nil {
		panic("exchptr is nil")
	}
	exch := &Exchange{exchptr}
	runtime.SetFinalizer(exch, func(exch *Exchange) {
		if exch.ptr != nil {
			exch.End()
			exch.ptr = nil
		}
	})
	return exch
}

type Session struct {
	ptr sessionPtr
}

func WrapSession(sessptr unsafe.Pointer) *Session {
	return createSession(sessionPtr(sessptr))
}

func createSession(sessptr sessionPtr) *Session {
	if sessptr == nil {
		panic("sessptr is nil")
	}
	sess := &Session{sessptr}
	runtime.SetFinalizer(sess, func(sess *Session) {
		if sess.ptr != nil {
			sess.HangUp()
			sess.ptr = nil
		}
	})
	return sess
}

type CallHandler interface {
	Handle(call *Call)
}

type CallHandlerFunc func(call *Call)

func (f CallHandlerFunc) Handle(call *Call) {
	f(call)
}

type ClientDataConstructor func()interface{}
type ClientDataDestructor func(interface{})

// Exchange management style
type ExchMgmt int

const (
	// No explicit exchange management
	EXCHANGE_ATOMIC = ExchMgmt(syscall.ASYNC_EXCHANGE_ATOMIC)
	// Exchange management via phone cloning
	EXCHANGE_PARALLEL = ExchMgmt(syscall.ASYNC_EXCHANGE_PARALLEL)
	// Exchange management via mutual exclusion
	EXCHANGE_SERIALIZE = ExchMgmt(syscall.ASYNC_EXCHANGE_SERIALIZE)
)

var (
	clientConnectionClosure func(cid syscall.IpcCallidT, call *callData, _ unsafe.Pointer)
	interruptReceivedClosure func(cid syscall.IpcCallidT, call *callData)
)

/* Currently unused.

func unpackArgs(args []syscall.Arg) (arg1, arg2, arg3, arg4, arg5 syscall.Arg) {
	switch len(args) {
	default:
		panic("Invalid number of arguments.")
	case 5:
		arg5 = args[4]
		fallthrough
	case 4:
		arg4 = args[3]
		fallthrough
	case 3:
		arg3 = args[2]
		fallthrough
	case 2:
		arg2 = args[1]
		fallthrough
	case 1:
		arg1 = args[0]
		fallthrough
	case 0:
	}
	return
}
*/

func SetClientDataConstructor(ctor ClientDataConstructor) {
	if ctor == nil {
		panic("Invalid argument to async.SetClientDataConstructor.")
	}

	// We can do this because the constructor is never supposed to be released.
	syscall.LockObject(unsafe.Pointer(&ctor))

	closure := func()unsafe.Pointer {
		// XXX: an ugly little hack; make sure the fibril is registered with the runtime
		s := fibril_setup()

		data := new(interface{})
		(*data) = ctor()
		if *data == nil {
			return nil
		}

		syscall.LockObject(unsafe.Pointer(data))
		if s {
			fibril_teardown()
		}
		return unsafe.Pointer(data)
	}

	async_set_client_data_constructor(closure)

	syscall.LockObject(unsafe.Pointer(&closure))
}

func SetClientDataDestructor(dtor ClientDataDestructor) {
	if dtor == nil {
		panic("Invalid argument to async.SetClientDataDestructor.")
	}

	// We can do this because the destructor is never supposed to be released.
	syscall.LockObject(unsafe.Pointer(&dtor))

	closure := func(ptr unsafe.Pointer) {
		// XXX: an ugly little hack; make sure the fibril is registered with the runtime
		s := fibril_setup()

		if (ptr != nil) {
			syscall.UnlockObject(ptr)
			data := (*interface{})(ptr)
			dtor(*data)
			*data = nil
			data = nil
		}

		if s {
			fibril_teardown()
		}
	}

	async_set_client_data_destructor(closure)

	syscall.LockObject(unsafe.Pointer(&closure))
}

func GetClientData() interface{} {
	return *(*interface{})(async_get_client_data())
}

func GetClientDataByID(taskid syscall.TaskID) interface{} {
	return *(*interface{})(async_get_client_data_by_id(taskid))
}

func Manager() {
	fibril.Switch(fibril.TO_MANAGER)
}

func USleep(useconds int) {
	async_usleep(syscall.SusecondsT(useconds))
}

func CreateManager() {
	async_create_manager()
}

func DestroyManager() {
	async_destroy_manager()
}

func SetClientConnection(handler CallHandler) {
	if (handler == nil) {
		panic("handler is nil")
	}

	syscall.LockObject(unsafe.Pointer(&handler))

	closure := func(cid syscall.IpcCallidT, call *callData, _ unsafe.Pointer) {
		s := fibril_setup()

		handler.Handle(&Call{cid, *call})

		if s {
			fibril_teardown()
		}
	}
	async_set_client_connection(closure)

	// Make sure the closure is referenced in Go code for as long as it's used in C code.
	clientConnectionClosure = closure
}

func SetInterruptReceived(handler CallHandler) {
	if (handler == nil) {
		panic("handler is nil")
	}

	closure := func(cid syscall.IpcCallidT, call *callData) {
		s := fibril_setup()

		handler.Handle(&Call{cid, *call})

		if s {
			fibril_teardown()
		}
	}
	async_set_interrupt_received(closure)

	// Make sure the closure is referenced in Go code for as long as it's used in C code.
	interruptReceivedClosure = closure
}

func GetCall() (call *Call) {
	call, _ = GetCallTimeout(0)
	return
}

func GetCallTimeout(useconds int) (call *Call, e error) {
	var cdata callData
	callid := async_get_call_timeout(&cdata, syscall.SusecondsT(useconds))
	if callid == 0 {
		return nil, syscall.ETIMEOUT
	}

	return &Call{callid, cdata}, nil
}



/* TODO: Figure out how exactly is this used.

func fibrilStartFunc(cid syscall.IpcCallidT, call *CallData, arg unsafe.Pointer) {
	fibril_setup()
	handler := *(*CallHandler)arg
	handler.Handle(cid, call)
	// FIXME: What happens if Handle panics?
	fibril_teardown()
}

func NewConnection(in_task_id syscall.TaskID, in_phone_hash syscall.Arg, callid CallID, call *CallData,
	handler CallHandler) fibril.ID {

	return async_new_connection(in_task_id, in_phone_hash, callid, call, fibrilStartFunc, unsafe.Pointer(&handler))
}
*/

// Wrapper for answer routines.
// XXX: This wraps the ipc_answer_fast/slow directly, so as not to create another level of indirection.

func (call *Call) Answer(rc syscall.Errno, arg1, arg2, arg3, arg4, arg5 syscall.Arg) syscall.Errno {
	if arg5 == 0 {
		return syscall.Errno(ipc_answer_fast(call.id, syscall.Arg(rc), arg1, arg2, arg3, arg4))
	}
	return syscall.Errno(ipc_answer_slow(call.id, syscall.Arg(rc), arg1, arg2, arg3, arg4, arg5))
}

// Wrapper for forwarding routines.

func (call *Call) Forward(exch *Exchange, method, arg1, arg2, arg3, arg4, arg5 syscall.Arg, mode uint) syscall.Errno {
	rc := 0
	if arg3 == 0 && arg4 == 0 && arg5 == 0 {
		rc = async_forward_fast(call.id, exch.ptr, method, arg1, arg2, mode)
	} else {
		rc = async_forward_slow(call.id, exch.ptr, method, arg1, arg2, arg3, arg4, arg5, mode)
	}
	return syscall.Errno(rc)
}

// Wrapper for simple communication.

func (exch *Exchange) Message(method, arg1, arg2, arg3, arg4, arg5 syscall.Arg) {
	if arg5 == 0 {
		async_msg_4(exch.ptr, method, arg1, arg2, arg3, arg4)
	} else {
		async_msg_5(exch.ptr, method, arg1, arg2, arg3, arg4, arg5)
	}
}


func (exch *Exchange) CloneEstablish(mgmt ExchMgmt) (rc syscall.Errno, sess *Session) {
	sessptr := async_clone_establish(mgmt, exch.ptr)
	if sessptr == nil {
		return syscall.GetErrno(), nil
	}
	return syscall.EOK, createSession(sessptr)
}

func (exch *Exchange) ConnectMeTo(mgmt ExchMgmt, arg1, arg2, arg3 syscall.Arg) (rc syscall.Errno, sess *Session) {
	sessptr := async_connect_me_to(mgmt, exch.ptr, arg1, arg2, arg3)
	if sessptr == nil {
		return syscall.GetErrno(), nil
	}
	return syscall.EOK, createSession(sessptr)
}

func (exch *Exchange) ConnectMeToBlocking(mgmt ExchMgmt, arg1, arg2, arg3 syscall.Arg) (rc syscall.Errno, sess *Session) {
	sessptr := async_connect_me_to_blocking(mgmt, exch.ptr, arg1, arg2, arg3)
	if sessptr == nil {
		return syscall.GetErrno(), nil
	}
	return syscall.EOK, createSession(sessptr)
}

func ConnectKBox(taskid syscall.TaskID) (rc syscall.Errno, sess *Session) {
	sessptr := async_connect_kbox(taskid)
	if sessptr == nil {
		return syscall.GetErrno(), nil
	}
	return syscall.EOK, createSession(sessptr)
}

func (exch *Exchange) ConnectToMe(arg1, arg2, arg3 syscall.Arg, handler CallHandler) syscall.Errno {

	if handler == nil {
		return syscall.Errno(async_connect_to_me(exch.ptr, arg1, arg2, arg3, nil, nil))
	}

	sem := make(chan int)

	closure := func(cid syscall.IpcCallidT, call *callData, _ unsafe.Pointer) {
		fibril_setup()

		// Signal the parent it can return now.
		sem <- 1
		sem = nil

		handler.Handle(&Call{cid, *call})
		// FIXME: What happens on panic?
		fibril_teardown()
	}

	psem := sem

	rc := async_connect_to_me(exch.ptr, arg1, arg2, arg3, closure, nil)

	// Wait for the child to register itself with the runtime.
	<-psem

	return syscall.Errno(rc)
}

func (sess *Session) SetArgs(arg1, arg2, arg3 syscall.Arg) {
	async_sess_args_set(sess.ptr, arg1, arg2, arg3)
}

func (sess *Session) HangUp() syscall.Errno {
	if sess.ptr == nil {
		return syscall.EINVAL
	}
	rc := syscall.Errno(async_hangup(sess.ptr))
	sess.ptr = nil
	runtime.SetFinalizer(sess, nil)
	return rc
}

func Poke() {
	async_poke()
}

func (sess *Session) ExchangeBegin() *Exchange {
	if sess.ptr == nil {
		panic("invalid session")
	}

	exchptr := async_exchange_begin(sess.ptr)
	if exchptr == nil {
		return nil
	}
	return createExchange(exchptr)
}

func (exch *Exchange) End() {
	if exch.ptr != nil {
		async_exchange_end(exch.ptr)
		exch.ptr = nil
		runtime.SetFinalizer(exch, nil)
	}
}

func (clone_exch *Exchange) Clone(via Exchange) syscall.Errno {
	rc := async_exchange_clone(via.ptr, clone_exch.ptr)
	return syscall.Errno(rc)
}

func CloneReceive(mgmt ExchMgmt) *Session {
	sessptr := async_clone_receive(mgmt)
	if sessptr == nil {
		return nil
	}
	return createSession(sessptr)
}

func CallbackReceive(mgmt ExchMgmt) *Session {
	sessptr := async_callback_receive(mgmt)
	if sessptr == nil {
		return nil
	}
	return createSession(sessptr)
}
